package org.test.security;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.test.token.ChickReferenceTokenEnhancer;
import org.test.token.ChickSelfContainedTokenEnhancer;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

@Configuration
public class SecurityConfigBeans {
    // jwt-token扩展
    private final ChickSelfContainedTokenEnhancer chickSelfContainedTokenEnhancer;
    // id-token扩展
    private final ChickReferenceTokenEnhancer chickReferenceTokenEnhancer;
    // 数据库链接
    private final JdbcTemplate jdbcTemplate;
    // 密码编码器
    private final PasswordEncoder passwordEncoder;
    // jwt编码器
    private final JwtEncoder jwtEncoder;
    // 客户端管理
    private final RegisteredClientRepository registeredClientRepository;
    // 用户检索
    private final UserDetailsService userDetailsService;

    public SecurityConfigBeans(ChickSelfContainedTokenEnhancer chickSelfContainedTokenEnhancer,
                               ChickReferenceTokenEnhancer chickReferenceTokenEnhancer,
                               JdbcTemplate jdbcTemplate,
                               @Lazy PasswordEncoder passwordEncoder,
                               @Lazy JwtEncoder jwtEncoder,
                               @Lazy RegisteredClientRepository registeredClientRepository,
                               @Lazy UserDetailsService userDetailsService) {
        this.chickSelfContainedTokenEnhancer = chickSelfContainedTokenEnhancer;
        this.chickReferenceTokenEnhancer = chickReferenceTokenEnhancer;
        this.jdbcTemplate = jdbcTemplate;
        this.passwordEncoder = passwordEncoder;
        this.jwtEncoder = jwtEncoder;
        this.registeredClientRepository = registeredClientRepository;
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * JWT 编码器
     * 这里说一下解码器怎么操作 如果授权服务器和资源服务器在同一个应用中，直接原地使用jwkSource即可
     * 如果资源服务器和授权不服务器不在同一个上面 则使用下面的方法
     *
     * @Bean
     * public JwtDecoder jwtDecoder() {
     * // 指定授权服务器的 jwks 端点
     * String jwkSetUri = "http://ip:port/oauth2/jwks";
     * return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
     * }
     * 指定授权服务器的oauth2/jwks端点即可
     **/
    @Bean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        return new NimbusJwtEncoder(jwkSource);
    }

    /**
     * 配置授权服务器的基本属性 这里默认
     * 常见用途：
     * 自定义端点路径
     * 在实际应用中，通常需要修改默认的端点路径以满足公司安全策略或与其他系统集成的需求。
     * <p>
     * 集成 OpenID Connect (OIDC)
     * 在使用 OIDC 协议时，可以通过 AuthorizationServerSettings 配置 OIDC 所需的端点，如用户信息端点和 JWK 端点。
     * <p>
     * 支持多种 OAuth 2.0 流程
     * 配置授权码流程、客户端凭证流程、资源所有者密码流程等所需的端点。
     * <p>
     * 简化开发和维护
     * 将授权服务器的元信息集中配置在一个类中，便于管理和统一设置。
     **/
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    /**
     * 密码编码器
     **/
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * token生成器。配置要使用的token生成器
     **/
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator() {
        // 当客户端的tokenSetting的OAuth2TokenFormat设置为OAuth2TokenFormat.SELF_CONTAINED时 使用下面的
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);// jwtToken生成器（当客户端的token格式为self-contained时使用）
        jwtGenerator.setJwtCustomizer(chickSelfContainedTokenEnhancer);// 设置jwt-token自定义扩展

        // 当客户端的tokenSetting的OAuth2TokenFormat设置为OAuth2TokenFormat.REFERENCE 使用下面的
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();// 不透明的token生成器
        accessTokenGenerator.setAccessTokenCustomizer(chickReferenceTokenEnhancer);// 设置id-token自定义扩展

        // refreshToken生成器
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();// refreshToken生成器
        return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }

    /**
     * 用户管理类 （这里注册bean只是临时测试用的 实际要实现UserDetailsService，写查询用用户的逻辑，注入到spring中）
     **/
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withUsername("admin")
                .password(passwordEncoder.encode("qq10086.."))
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }

    /**
     * 用户检索详细实现类类
     **/
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider customerDaoAuthenticationProvider = new DaoAuthenticationProvider();
        // 设置userDetailsService
        customerDaoAuthenticationProvider.setUserDetailsService(userDetailsService);
        // 禁止隐藏用户未找到异常
        customerDaoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        // 使用MD5进行密码的加密
        customerDaoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return customerDaoAuthenticationProvider;
    }

    /**
     * 客户端管理类，管理客户端，按需配置
     **/
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        return new JdbcRegisteredClientRepository(jdbcTemplate); // 使用数据库

        /*
        // 创建客户端
        TokenSettings tokenSettings = TokenSettings.builder()
                .accessTokenTimeToLive(Duration.ofHours(1)) // 设置访问令牌有效期为1小时
                .refreshTokenTimeToLive(Duration.ofDays(30)) // 设置刷新令牌有效期为30天
                //.accessTokenFormat(OAuth2TokenFormat.REFERENCE) // 这个设置是开启不透明token
                .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) // 使用透明token
                .build();
        RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("chick")
                .clientSecret(passwordEncoder().encode("qq10086.."))
                // 允许的客户端登录方式 client_credentials
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
                // 客户端允许的授权类型
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(CustomAuthorizationGrantType.PASSWORD)
                .redirectUri("https://www.baidu.com")
                .postLogoutRedirectUri("http://127.0.0.1:8000")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .tokenSettings(tokenSettings)
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        */
        //// jdbcRegisteredClientRepository.save(client); // 第一此启动可以打开这个把客户端存到数据库，便于后续测试使用数据库
        //return new InMemoryRegisteredClientRepository(client); // 使用内存
    }

    /**
     * 用户登录信息管理类 管理OAuth2Authorization，按需配置
     **/
    @Bean
    public OAuth2AuthorizationService oAuth2AuthorizationService() {
        // return new InMemoryOAuth2AuthorizationService(); 使用内存
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); // 使用数据库
    }
}
